<?php

namespace DreamCat\FrameCore\Factory\Impl\ActionParam;

use DreamCat\AnnotationParser\AnnotationDocParse;
use DreamCat\AnnotationParser\DocIterator\DefaultDocIterator;
use DreamCat\Array2Class\Array2ClassInterface;
use DreamCat\Array2Class\Exception\ConvertFailed;
use DreamCat\FrameCore\Annotation\FuncParser\CtlMethodParamParser;
use DreamCat\FrameCore\Annotation\OutputFormater\ControllerMethodParseOutput;
use DreamCat\FrameInterface\Controller\ActionParamFactory;
use Dreamcat\PropertyAnalysis\Pojo\BuildinType;
use Dreamcat\PropertyAnalysis\Utils\BuildinTypeHelper;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionMethod;
use ReflectionParameter;
use RuntimeException;

/**
 * 默认的控制器方法参数生成器
 * @author vijay
 */
class DefaultActionParamFactory implements ActionParamFactory
{
    /** @var AnnotationDocParse 控制器方法文档解析器 */
    private $docParser;
    /**
     * @Autowire
     * @var Array2ClassInterface 数组转类的转换器
     */
    private $array2Class;

    /**
     * 生成要调用的参数列表
     * @param ReflectionMethod $method 控制器方法反射
     * @param ServerRequestInterface $request 服务器请求对象
     * @param array $pathVariables 占位符列表
     * @return array 参数列表
     */
    public function createParams(ReflectionMethod $method, ServerRequestInterface $request, array $pathVariables): array
    {
        $docParseResult = $this->getDocParser()->parse($method->getDocComment());
        $result = [];
        $requestBody = strval($request->getBody());
        foreach ($method->getParameters() as $parameter) {
            switch ($docParseResult[$parameter->name]["key"] ?? null) {
                case CtlMethodParamParser::AK_REQUEST_BODY:
                    $val = $this->assignRequestBody(
                        $parameter,
                        $request->getHeader("content-type")[0] ?? "",
                        $requestBody,
                        $docParseResult[$parameter->name]["name"] ?? null,
                        $request->getParsedBody()
                    );
                    break;
                case CtlMethodParamParser::AK_PATH_VARIABLE:
                    $val = $this->assignArrayVariable(
                        $parameter,
                        $pathVariables,
                        $docParseResult[$parameter->name]["name"] ?? null
                    );
                    break;
                case CtlMethodParamParser::AK_GET_PARAM:
                    $val = $this->assignArrayVariable(
                        $parameter,
                        $request->getQueryParams(),
                        $docParseResult[$parameter->name]["name"] ?? null
                    );
                    break;
                default:
                    $val = $this->autoValue($parameter, $pathVariables, $request);
            }
            $result[] = $val;
        }
        return $result;
    }

    /**
     * @return AnnotationDocParse 控制器方法文档解析器
     */
    protected function getDocParser(): AnnotationDocParse
    {
        if (!$this->docParser) {
            $this->docParser = new AnnotationDocParse();
            $this->docParser
                ->setDocCommentIterator(new DefaultDocIterator())
                ->setOutputFormater(new ControllerMethodParseOutput())
                ->addAnnotationParse(new CtlMethodParamParser());
        }
        return $this->docParser;
    }

    /**
     * 根据RequestBody注解从请求中获取数据
     * @param ReflectionParameter $parameter 函数参数信息
     * @param string $contentType content-type
     * @param string $requestBody http请求信息消息体
     * @param string $name 信息是数组时表示字段名称
     * @param null|array|object $parsedBody 解析后的数据
     * @return mixed
     */
    protected function assignRequestBody(
        ReflectionParameter $parameter,
        string $contentType,
        string $requestBody,
        ?string $name,
        $parsedBody
    ) {
        if ($requestBody == "") {
            return $this->dealNotFound($parameter);
        }
        $type = $parameter->getType();
        if ($type && $type->getName() == "string" && $name === null) {
            return $requestBody;
        }
        $body = $this->getParsedBody($contentType, $requestBody, $parsedBody);
        if ($name !== null) {
            $body = $body[$name] ?? null;
        }
        return $this->parseData($parameter, $body);
    }

    /**
     * 在找不到数据时的默认处理
     * @param ReflectionParameter $parameter 函数参数信息
     * @return mixed
     */
    protected function dealNotFound(ReflectionParameter $parameter)
    {
        if ($parameter->isDefaultValueAvailable()) {
            /** @noinspection PhpUnhandledExceptionInspection */
            return $parameter->getDefaultValue();
        }
        if ($parameter->allowsNull()) {
            return null;
        }
        $class = $parameter->getClass();
        if ($class) {
            $constructor = $class->getConstructor();
            if (!$constructor || $constructor->getNumberOfRequiredParameters() == 0) {
                return $class->newInstance();
            }
        } else {
            # 不允许为 null 且又没有类标注，肯定有内置类型标注
            $type = $parameter->getType();
            if (isset(BuildinTypeHelper::DEFAULT_VALUE[$type->getName()])) {
                return BuildinTypeHelper::DEFAULT_VALUE[$type->getName()];
            }
        }
        $ctl = $parameter->getDeclaringClass()->name;
        $method = $parameter->getDeclaringFunction()->name;
        $paramName = $parameter->name;
        throw new RuntimeException("无法构建 {$ctl}::{$method}::{$paramName} 参数");
    }

    /**
     * 获取解析后的消息体
     * @param string $contentType content-type
     * @param string $requestBody http请求信息消息体
     * @param null|array|object $parsedBody 解析后的数据
     * @return array 解析后的数据数组化
     */
    protected function getParsedBody(string $contentType, string $requestBody, $parsedBody): array
    {
        if ($parsedBody) {
            return (array)$parsedBody;
        }
        $contentType = strtolower($contentType);
        if (strpos($contentType, "application/x-www-form-urlencoded") !== false) {
            parse_str($requestBody, $body);
            return $body;
        } elseif (strpos($contentType, "application/json") !== false) {
            $body = json_decode($requestBody, true);
            $lastError = json_last_error();
            if ($lastError) {
                throw new RuntimeException("json decode error: " . json_last_error_msg(), $lastError);
            }
            return $body;
        }
        throw new RuntimeException("无法解析消息体, type = {$contentType}");
    }

    /**
     * 解析数据，生成参数
     * @param ReflectionParameter $parameter 参数反射
     * @param mixed $body 初步处理过的数据
     * @return mixed 参数
     */
    protected function parseData(ReflectionParameter $parameter, $body)
    {
        if ($body === null) {
            return $this->dealNotFound($parameter);
        }
        $type = $parameter->getType();
        if ($type) {
            switch ($type->getName()) {
                case BuildinType::INT:
                    return intval($body);
                case BuildinType::FLOAT:
                    return floatval($body);
                case BuildinType::STRING:
                    return is_scalar($body) ? strval($body) : serialize($body);
                case BuildinType::BOOL:
                    return boolval($body);
                case BuildinType::ARRAY:
                    return is_array($body) ? $body : [$body];
                case BuildinType::CALLABLE:
                    return function () use ($body) {
                        return $body;
                    };
                case "stdClass":
                    if (is_array($body)) {
                        return (object)$body;
                    }
            }
        }
        $class = $parameter->getClass();
        if (!$class && !$type) {
            # 什么标注也没有就直接返回得到的 body 部分
            return $body;
        }
        return $this->assignClassProperty($parameter, $body);
    }

    /**
     * 使用转换器生成参数
     * @param ReflectionParameter $parameter 参数反射
     * @param mixed $data 输入的数据
     * @return mixed 生成的参数
     */
    protected function assignClassProperty(ReflectionParameter $parameter, $data)
    {
        try {
            return $this->getArray2Class()->convertByRef($data, $parameter->getClass());
        } catch (ConvertFailed $exception) {
            return $this->dealNotFound($parameter);
        }
    }

    /**
     * @return Array2ClassInterface 数组转类的转换器
     */
    public function getArray2Class(): Array2ClassInterface
    {
        return $this->array2Class;
    }

    /**
     * @param Array2ClassInterface $array2Class 数组转类的转换器
     * @return static 对象本身
     */
    public function setArray2Class(Array2ClassInterface $array2Class): DefaultActionParamFactory
    {
        $this->array2Class = $array2Class;
        return $this;
    }

    /**
     * 根据 PathVariable 或 GetParam 注解从请求中获取数据
     * @param ReflectionParameter $parameter 函数参数信息
     * @param array $arrayVariables 占位符列表
     * @param string $name 信息是数组时表示字段名称
     * @return mixed
     */
    protected function assignArrayVariable(
        ReflectionParameter $parameter,
        array $arrayVariables,
        ?string $name
    ) {
        $data = $name === null ? $arrayVariables : $arrayVariables[$name] ?? null;
        return $this->parseData($parameter, $data);
    }

    /**
     * 无注解时从请求中获取数据
     * @param ReflectionParameter $parameter 函数参数反射
     * @param array $pathVariables 占位符列表
     * @param ServerRequestInterface $request http请求参数
     * @return mixed
     */
    protected function autoValue(
        ReflectionParameter $parameter,
        array $pathVariables,
        ServerRequestInterface $request
    ) {
        # 先尝试在占位符查找
        if (isset($pathVariables[$parameter->name])) {
            return $this->assignArrayVariable($parameter, $pathVariables, $parameter->name);
        }
        # 再尝试在GET中查询
        $get = $request->getQueryParams();
        if (isset($get[$parameter->name])) {
            return $this->assignArrayVariable($parameter, $get, $parameter->name);
        }
        return $this->dealNotFound($parameter);
    }
}

# end of file
